﻿using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Kugar.Core.ExtMethod;
using Kugar.Core.Log;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.CSharp;

namespace Kugar.UI.RazorUI.RazorEngine
{
    internal class RazorRenderEngine
    {
        private static RazorRenderEngine _default = null;
        
        private RazorProjectEngine _engine = null;

        private string _defaultNameSpace = "";// Assembly.GetExecutingAssembly().GetType("Program").Namespace;
        
        public RazorRenderEngine(string folderPath)
        {
            var exe = Assembly.GetExecutingAssembly().GetTypes().Where(x=>x.Name.CompareTo("Program",true)).First();

            _defaultNameSpace = exe.Namespace;

            _engine= CreateProjectEngine(_defaultNameSpace, folderPath);


        }
        
        public string Execute(string viewPath, object model,ViewContext viewContext)
        {
            var view = GenerateViewInstance(viewPath,model); ;
            view.Model = model;
            view.ViewContext = viewContext;
            view.ExecuteAsync().Wait();

            RazorViewBase currentView = view;

            currentView.ViewContext = viewContext;

            do
            {
                if (!string.IsNullOrWhiteSpace(currentView.Layout))
                {
                    RazorViewBase layoutView = GenerateViewInstance(currentView.Layout,model);
                    layoutView.Model = model;
                    layoutView.ChildView = currentView;
                    layoutView.ViewContext = viewContext;

                    layoutView.ExecuteAsync().Wait();
                    
                    currentView = layoutView;
                    
                }
                else
                {
                    break;
                }
            } while (true);

            return currentView.Value.ToString();
        }

        private Type completeCode(string code)
        {
            ICodeCompiler comp = new CSharpCodeProvider().CreateCompiler();

            //编译器的传入参数   
            CompilerParameters cp = new CompilerParameters();
          
            
            cp.ReferencedAssemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().Where(x=>!string.IsNullOrWhiteSpace(x.Location) && !x.GlobalAssemblyCache).Select(x=>x.Location).Distinct().ToArrayEx());
            
            cp.GenerateExecutable = false; //不生成可执行文件   
            cp.GenerateInMemory = true; //在内存中运行   
            cp.IncludeDebugInformation = true;

            CompilerResults results = comp.CompileAssemblyFromSource(cp, code);

            if (results.Errors.HasErrors)
            {
                foreach (CompilerError error in results.Errors)
                {
                    LoggerManager.Default.Debug("生成类出错:" + error.ErrorText);
                }

                return null;
            }

            var type = results.CompiledAssembly.GetTypes()
                .Where(x => x.IsSubclassOf(typeof(RazorViewBase))).FirstOrDefault();

            return type;
        }

        //private Type completeCode(string code)
        //{
        //    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);


        //    string assemblyName = Path.GetRandomFileName();
        //    MetadataReference[] references = null;

        //    try
        //    {
        //        references = AppDomain.CurrentDomain.GetAssemblies()
        //            .Where(x=>!string.IsNullOrWhiteSpace(x.Location))
        //            .Select(x => MetadataReference.CreateFromFile(x.Location)).ToArray();
                
        //    }
        //    catch (Exception e)
        //    {
        //        Console.WriteLine(e);
        //        throw;
        //    }

        //    CSharpCompilation compilation = CSharpCompilation.Create(
        //        assemblyName,
        //        syntaxTrees: new[] {syntaxTree},
        //        references: references,
        //        options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        //    );

        //    using (var ms = new MemoryStream())
        //    {
        //        EmitResult result = compilation.Emit(ms);

        //        if (!result.Success)
        //        {
        //            IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
        //                diagnostic.IsWarningAsError ||
        //                diagnostic.Severity == DiagnosticSeverity.Error);

        //            foreach (Diagnostic diagnostic in failures)
        //            {
        //                Console.Error.WriteLine("\t{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
        //            }

        //            return null;
        //        }
        //        else
        //        {
        //            ms.Seek(0, SeekOrigin.Begin);

        //            Assembly assembly = Assembly.Load(ms.ReadAllBytes());

        //            var type = assembly.GetTypes()
        //                .Where(x => x.IsSubclassOf(typeof(RazorViewBase))).FirstOrDefault();

        //            return type;

        //        }
        //    }
        //}

        private RazorProjectEngine CreateProjectEngine(string rootNamespace, string targetProjectDirectory, Action<RazorProjectEngineBuilder> configure = null)
        {
            var fileSystem = RazorProjectFileSystem.Create(targetProjectDirectory);

            var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder =>
            {
                builder
                    .SetNamespace(rootNamespace)
                    .SetBaseType("Kugar.UI.RazorUI.RazorEngine.RazorViewBase")
                    .ConfigureClass((document, @class) =>
                    {
                        @class.ClassName = Path.GetFileNameWithoutExtension(document.Source.FilePath);
                        @class.Modifiers.Clear();
                        @class.Modifiers.Add("public");
                        
                    })
                    
                    ;

                FunctionsDirective.Register(builder);
                InheritsDirective.Register(builder);
                SectionDirective.Register(builder);

                var p = builder.Features.OfType<ITagHelperFeature>();

                builder.Features.Add(new SuppressChecksumOptionsFeature());
                builder.Features.Add(new SuppressMetadataAttributesFeature());

                if (configure != null)
                {
                    configure(builder);
                }

                builder.AddDefaultImports(@"
                    @using System
                    @using System.Threading.Tasks
                    @using Kugar.UI.RazorUI

                ");
            });
            return projectEngine;
        }

        private RazorViewBase GenerateViewInstance(string viewPath,object model)
        {
            RazorViewBase view = null;

            var type = GenerateViewType(viewPath);

            if (type.IsGenericType)
            {
                view = (RazorViewBase)Fasterflect.ConstructorExtensions.CreateInstance(type, type.GenericTypeArguments);
            }
            else
            {
                view =(RazorViewBase) Fasterflect.ConstructorExtensions.CreateInstance(type);
            }

            return view;
        }

        private Type GenerateViewType(string viewPath)
        {
            var viewCode = GenerateCodeFile(_engine, viewPath);

            var type = completeCode(viewCode);

            return type;
        }

        private string GenerateCodeFile(RazorProjectEngine projectEngine,string viewFilePath)
        {
            var cshtmlFile = projectEngine.FileSystem.GetItem(viewFilePath);

            return GenerateCodeFile(projectEngine, cshtmlFile);
        }

        private string GenerateCodeFile(RazorProjectEngine projectEngine, RazorProjectItem projectItem)
        {
            var projectItemWrapper = new FileSystemRazorProjectItemWrapper(projectItem);
            var codeDocument = projectEngine.Process(projectItemWrapper);
            var opt = codeDocument.GetCodeGenerationOptions();

            var cSharpDocument = codeDocument.GetCSharpDocument();

            if (cSharpDocument.Diagnostics.Any())
            {
                var diagnostics = string.Join(Environment.NewLine, cSharpDocument.Diagnostics);
                Console.WriteLine($"One or more parse errors encountered. This will not prevent the generator from continuing: {Environment.NewLine}{diagnostics}.");
            }

            //var generatedCodeFilePath = Path.ChangeExtension(projectItem.PhysicalPath, ".Designer.cs");

            return cSharpDocument.GeneratedCode;
        }

        private class SuppressChecksumOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
        {
            public int Order { get; set; }

            public void Configure(RazorCodeGenerationOptionsBuilder options)
            {
                if (options == null)
                {
                    throw new ArgumentNullException(nameof(options));
                }

                options.SuppressChecksum = true;
            }
        }

        private class SuppressMetadataAttributesFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
        {
            public int Order { get; set; }

            public void Configure(RazorCodeGenerationOptionsBuilder options)
            {
                if (options == null)
                {
                    throw new ArgumentNullException(nameof(options));
                }

                options.SuppressMetadataAttributes = true;
            }
        }

        private class FileSystemRazorProjectItemWrapper : RazorProjectItem
        {
            private readonly RazorProjectItem _source;

            public FileSystemRazorProjectItemWrapper(RazorProjectItem item)
            {
                _source = item;
            }

            public override string BasePath => _source.BasePath;

            public override string FilePath => _source.FilePath;

            // Mask the full name since we don't want a developer's local file paths to be commited.
            public override string PhysicalPath => _source.FileName;

            public override bool Exists => _source.Exists;

            public override Stream Read()
            {
                var processedContent = ProcessFileIncludes();
                return new MemoryStream(Encoding.UTF8.GetBytes(processedContent));
            }

            private string ProcessFileIncludes()
            {
                var basePath = System.IO.Path.GetDirectoryName(_source.PhysicalPath);
                var cshtmlContent = File.ReadAllText(_source.PhysicalPath);

                var startMatch = "<%$ include: ";
                var endMatch = " %>";
                var startIndex = 0;
                while (startIndex < cshtmlContent.Length)
                {
                    startIndex = cshtmlContent.IndexOf(startMatch, startIndex);
                    if (startIndex == -1)
                    {
                        break;
                    }
                    var endIndex = cshtmlContent.IndexOf(endMatch, startIndex);
                    if (endIndex == -1)
                    {
                        throw new InvalidOperationException($"Invalid include file format in {_source.PhysicalPath}. Usage example: <%$ include: ErrorPage.js %>");
                    }
                    var includeFileName = cshtmlContent.Substring(startIndex + startMatch.Length, endIndex - (startIndex + startMatch.Length));
                    Console.WriteLine("      Inlining file {0}", includeFileName);
                    var includeFileContent = File.ReadAllText(System.IO.Path.Combine(basePath, includeFileName));
                    cshtmlContent = cshtmlContent.Substring(0, startIndex) + includeFileContent + cshtmlContent.Substring(endIndex + endMatch.Length);
                    startIndex = startIndex + includeFileContent.Length;
                }
                return cshtmlContent;
            }
        }
        
        internal class DefaultRazorProjectItem : RazorProjectItem
        {
            /// <summary>
            /// Initializes a new instance of <see cref="T:Microsoft.AspNetCore.Razor.Language.DefaultRazorProjectItem" />.
            /// </summary>
            /// <param name="basePath">The base path.</param>
            /// <param name="relativePhysicalPath">The physical path of the base path.</param>
            /// <param name="filePath">The path.</param>
            /// <param name="file">The <see cref="T:System.IO.FileInfo" />.</param>
            public DefaultRazorProjectItem(
                string basePath,
                string filePath,
                string relativePhysicalPath,
                FileInfo file)
            {
                this.BasePath = basePath;
                this.FilePath = filePath;
                this.RelativePhysicalPath = relativePhysicalPath;
                this.File = file;
            }

            public FileInfo File { get; }

            public override string BasePath { get; }

            public override string FilePath { get; }

            public override bool Exists
            {
                get
                {
                    return this.File.Exists;
                }
            }

            public override string PhysicalPath
            {
                get
                {
                    return this.File.FullName;
                }
            }

            public override string RelativePhysicalPath { get; }

            public override Stream Read()
            {
                return (Stream)new FileStream(this.PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
            }
        }

    }
}
